 1.Druid 架构与原理之基础架构
   
   Druid 总体包含以下 6 类节点：
   1).Coordinator node：
   主要负责历史节点的数据负载均衡，以及通过规则管理数据的生命周期。协调节点告诉历
史节点加载新数据、卸载过期数据、复制数据、 和为了负载均衡移动数据。
   Coordinator 是周期性运行的（由 druid.coordinator.period 配置指定，默认执行间隔为 60s)；Coordinator
需要维护和 ZooKeeper 的连接,以获取集群的信息。Segment 和 Rule 的信息保存在元数据库中,所以也需要维护与元
数据库的连接。
   2).Overlord node：
   进程监视 MiddleManager 进程，并且是Druid 数据摄入的主节点。负责将提取任务分配给 MiddleManagers 
并协调 Segement 发布，包括接受、拆解、分配 Task，以及创建 Task 相关的锁，并返回 Task 的状态。
   3).Historical node：
   加载生成好的数据文件，以供数据查询。Historical node是整个集群查询性能的核心所在,Historical 会承担
绝大部分的 segment 查询。
   
   Historical 进程从 Deep Storage 中下载 Segment，并响应有关这些 Segment 的查询请求（这些请求来自Broker
进程）；
   Historical 进程不处理写入请求；
   Historical 进程采用了无共享架构设计,它知道如何去加载和删除 Segment,以及如何基于Segment来响应查询。即便
底层的深度存储无法正常工作，Historical 进程还是能针对其已同步的 Segments，正常提供查询服务。
   4).MiddleManager node：
   及时摄入实时数据，生成 Segment 数据文件。
   MiddleManager 进程是执行提交任务的工作节点。MiddleManagers 将任务转发给在不同 JVM 中运行的 Peon 进程。
   MiddleManager、Peon、Task 的对应关系是，每个 Peon 进程一次只能运行一个Task 任务，但一个 MiddleManager
却可以管理多个 Peon 进程。
   5).Broker node：
   接收客户端查询请求，并将这些查询转发给 Historicals 和 MiddleManagers。当 Brokers 从这些子查询中收到
结果时，它们会合并这些结果并将它们返回给调用者。
   
   Broker节点负责转发Client查询请求的；
   Broker通过zookeeper能够知道哪个Segment在哪些节点上，将查询转发给相应节点；
   所有节点返回数据后，Broker会将所有节点的数据进行合并，然后返回给Client；
   6).Router node（可选的）：
   负责将请求路由到Broker、Coordinators、Overlords。
   Router 进程可以在 Brokers、Overlords 和 Coordinators 进程之上，提供一层统一的 API网关。Router 进程是
可选的，如果集群数据规模已经达到了 TB级别，需要考虑启用（druid.router.managementProxy.enabled=true）。
一旦集群规模达到一定的数量级,那么发生故障的概率就会变得不容忽视,而 Router支持将请求只发送给健康的节点,避免
请求失败。同时,查询的响应时间和资源消耗,也会随着数据量的增长而变高,而Router支持设置查询的优先级和负载均衡
策略,避免了大查询造成的队列堆积或查询热点等问题。

   Druid 的进程可以被任意部署，为了理解与部署组织方便。这些进程分为了三类：
       Master: Coordinator、Overlord 负责数据可用性和摄取
	   Query: Broker and Router，负责处理外部请求
	   Data: Historical and MiddleManager，负责实际的Ingestion负载和数据存储
	   
   Druid 还包含 3 类外部依赖：
       1).Deep Storage：存放生成的 Segment 数据文件，并供历史服务器下载， 对于单节点集群可以是本地磁盘，而
对于分布式集群一般是 HDFS
       2).Metadata Storage,存储 Druid 集群的元数据信息，如 Segment 的相关信息，一般使用 MySQL
   表名 					作用
   druid_dataSource 		存储DataSources，以便 Kafka Index Service查找
   druid_pendingSegments 	存储 pending 的 Segments
   druid_segments 			存储每个 Segments 的metadata 信息
   druid_rules 				关于 Segment 的 load / drop 规则
   druid_config 			存放运行时的配置信息
   druid_tasks 				为Indexing Service 保存 Task 信息
   druid_tasklogs 			为Indexing Service 保存 Task 日志
   druid_tasklocks 			为Indexing Service 保存 Task 锁
   druid_supervisors 		为Indexing Service保存 SuperVisor 信息
   druid_audit 				记录配置、Coordinator规则的变化
   
	   3).Zookeeper：为 Druid 集群提供以执行协调服务。如内部服务的监控，协调和领导者选举
   
   Coordinator 节点的 Leader 选举
   Historical 节点发布 Segment 的协议
   Coordinator 和 Historical 之间 load / drop Segment 的协议
   Overlord 节点的 Leader 选举
   Overlord 和 MiddleManager 之间的 Task 管理
   
   架构演进
   Apache Druid 初始版本架构图 ~ 0.6.0(2012~2013)
   
   0.7.0 ~ 0.12.0（2013~2018）
   Apache Druid 旧架构图——数据流转
   
   Apache Druid 旧架构图——集群管理
   
   0.13.0 ~ 当前版本(2018~now)
   
   Lambda 架构
   从大的架构上看，Druid是一个Lambda架构。
   Lambda架构是由 Storm 的作者 Nathan Marz 提出的一个实时大数据处理框架。Lambda 架构设计是为了在处理大
规模数据时，同时发挥流处理和批处理的优势：
       通过批处理提供全面、准确的数据
	   通过流处理提供低延迟的数据
   从而达到平衡延迟、吞吐量和容错性的目的，为了满足下游的即席查询，批处理和流处理的结果会进行合并。
   
   Lambda 架构包含三层，Batch Layer、Speed Layer 和 Serving Layer：
       Batch Layer：批处理层。对离线的历史数据进行预计算，为了下游能够快速查询想要的结果。由于批处理基
于完整的历史数据集，准确性可以得到保证。批处理层可以用 Hadoop、Spark 和 Flink 等框架计算
       Speed Layer：加速处理层。处理实时的增量数据，这一层重点在于低延迟。加速层的数据不如批处理层那样
完整和准确,但是可以填补批处理高延迟导致的数据空白.加速层可以用Storm、Spark streaming和Flink 等框架计算
       Serving Layer：合并层。将历史数据、实时数据合并在一起，输出到数据库或者其他介质，供下游分析
   
   流式数据的链路为：
   Row data→Kafka→ Streaming processor(Optional,实时ETL)→ Kafka(Optional)→Druid→ Application /User   
   批处理数据的链路为：
   Raw data → Kafka(Optional) → HDFS → ETL process(Optional) → Druid → Application / User

 2.数据存储
   
   Druid中的数据存储在被称为DataSource中，DataSource类似RDBMS中的 Table
   每个DataSource按照时间划分，每个时间范围称为一个Chunk（比如按天分区，则一个chunk为一天）
   在Chunk中数据被分为一个或多个Segment
       Segment是数据实际存储结构，Datasource、Chunk只是一个逻辑概念
   Segment是按照时间组织成的Chunk，所以在按照时间查询数据时，效率非常高
   每个Segment都是一个单独的文件，通常包含几百万行数据
   
   数据分区：
	   Druid处理的是事件数据，每条数据都会带有一个时间戳，可以使用时间进行分区
	   上图指定了分区粒度为为天，那么每天的数据都会被单独存储和查询
	   
   Segment内部存储结构
	   Druid采用列式存储，每列数据都是在独立的结构中存储
	   Segment中的数据类型主要分为三种
	       时间戳。每一行数据，都必须有一个timestamp，Druid一定会基于时间戳来分片
		   维度列。用来过滤filter或者组合groupby的列，通常是string、float、double、int类型
		   指标列。用来进行聚合计算的列，指定的聚合函数 sum、average 等
   
   MiddleManager节点接受到 ingestion 的任务之后，开始创建Segment：
	   转换成列存储格式
	   用bitmap来建立索引（对所有的dimension列建立索引)
	   使用各种压缩算法：
		   所有的列使用LZ4压缩
		   所有的字符串列采用字典编码/标识以达到最小化存储
		   对位图索引使用位图压缩
   
   Segment创建完成之后,Segment文件就是不可更改的，被写入到深度存储（目的是为了防止MiddleManager节点
宕机后Segment的丢失)。 然后Segment会加载到Historical节点，Historical节点可以直接加载到内存中。
   
   同时，metadata store 也会记录下这个新创建的Segment的信息，如结构，尺寸，深度存储的位置等等。
Coordinator节点需要这些元数据来协调数据的查找。

 3.索引服务
   
   索引服务：数据导入并创建 segments 数据文件的服务
   索引服务是一个高可用的分布式服务，采用主从结构作为架构模式，索引服务由三大组件构成
	   overlord 作为主节点
	   middlemanager是从节点
	   peon用于运行一个task
   索引服务架构图如下图所示：
   
   索引服务由三部分组件组成：
	   Overlord组件
		   负责创建task、分发task到middlemanager上运行，为task创建锁以及跟踪task运行状态并反馈给用户
	   MiddleManager组件
	       作为从节点，负责接收主节点分配的任务，然后为每个task启动一个独立的JVM进程来完成具体的任务
	   Peon(劳工)组件
		   由middlemanager启动的一个进程用于运行一个task任务
		   
   索引服务架构与 Yarn 的架构类似：
	   Overlaod => ResourceManager，负责集群资源管理和任务分配
	   MiddleManager => NodeManager，负责接受任务和管理本节点的资源
	   Peon => Container，执行节点上具体的任务
   
   Task类型有很多，包括：
       index hadoop task：Hadoop索引任务，利用Hadoop集群执行MapReduce任务以完成segment数据文件的创
建，适合体量比较大的segments数据文件的创建任务
       index kafka task：用于Kafka数据的实时摄入，通过Kafka索引服务可以在Overlord上配置一个
KafkaSupervisor,通过管理Kafka索引任务的创建和生命周期来完成 Kafka 数据的摄取
       merge task：合并索引任务，将多个segments数据文件按照指定的聚合方法合并为一个segments数据文件
	   kill task : 销毁索引任务，将执行时间范围内的数据从Druid集群的深度存储中删除
	   
 4.索引及压缩机制
   
   Druid的查询时延低性能好的主要是因为采用了五个技术点：
	   数据预聚合
	   列式存储、数据压缩
	   Bitmap 索引
	   mmap(内存文件映射方式)
	   查询结果的中间缓存
   1).数据预聚合
   Druid通过一个roll-up的处理，将原始数据在注入的时候就进行汇总处理
   Roll-up可以压缩我们需要保存的数据量
   Druid会把选定的相同维度的数据进行聚合操作，可减少存储的大小
   Druid可以通过 queryGranularity 来控制注入数据的粒度。 最小的queryGranularity 是 millisecond(毫秒级)
   
   Roll-up聚合前：
   time 				AppKey 		area 	value
   2020-10-05 10:00:00  areakey1    Beijing   1
   2020-10-05 10:30:00  areakey1    Beijing   1
   2020-10-05 11:00:00  areakey1    Beijing   1
   2020-10-05 11:00:00  areakey2    Shanghai  2
   
   Roll-up聚合后：
   time 		AppKey 		area 		value
   2020-10-05   areakey1 	Beijing 	  3
   2020-10-05   areakey2 	Shanghai	  2
   2).位图索引
   Druid在摄入的数据示例：
   Time 				AppKey 		Area 	Value
   2020-10-01 10:00:00 	appkey1 	深圳 	  1
   2020-10-01 11:00:00 	appkey2 	北京 	  1
   2020-10-01 12:00:00 	appkey2 	深圳 	  1
   2020-10-01 13:00:00 	appkey2 	北京 	  1
   … … 					… … 		… … 	… …
   2020-10-01 23:00:00 	appkey3 	北京 	  1
   
       第一列为时间，Appkey和Area都是维度列，Value为指标列
	   Druid会在导入阶段自动对数据进行Rollup，将维度相同组合的数据进行聚合处理
	   数据聚合的粒度根据业务需要确定
   按天聚合后的数据如下：
   Time 		AppKey 		Area 	Value
   2020-10-01 	appkey1 	北京  	  10
   2020-10-01 	appkey1 	深圳  	  20
   2020-10-01 	appkey2 	北京  	  30
   2020-10-01 	appkey2 	深圳  	  40
   2020-10-01 	appkey3 	北京  	  50
   2020-10-01 	appkey3 	深圳  	  60
   Druid通过建立位图索引，实现快速数据查找。
   Bitmap 索引主要为了加速查询时有条件过滤的场景。Druid 在生成索引文件的时候，对每个列的每个取值生成对应
的 Bitmap 集合。如下图所示：
   Key           
   appkey1 	1 	1 	0 	0 	0 	0
   appkey2 	0 	0 	1 	1 	0 	0
   appkey3 	0 	0 	0 	0 	1 	1
   深圳 	0 	1 	0 	1 	0 	1
   北京 	1 	0 	1 	0 	1 	0
   
   索引位图可以看作是HashMap<String, Bitmap>
	   key就是维度的取值
	   value就是该表中对应的行是否有该维度的值
	   
   以SQL查询为例：
   1).boolean条件查询
select sum(value)
from tab1
where time='2020-10-01'
and appkey in ('appkey1', 'appkey2')
and area='北京'
   执行过程分析：
       根据时间段定位到segment
	   Appkey in ('appkey1', 'appkey2') and area='北京' 查到各自的bitmap
		   (appkey1 or appkey2) and 北京
		   (110000 or 001100) and 101010 = 111100 and 101010 = 101000
		   符合条件的列为：第1行 & 第3行，这几行 sum(value) 的和为 40
   2).group by 查询
   select area, sum(value)
from tab1
where time='2020-10-01'
and appkey in ('appkey1', 'appkey2')
group by area

   该查询与上面的查询不同之处在于将符合条件的列
	   appkey1 or appkey2
	   110000 or 001100 = 111100
	   将第1行 到 第4行取出来
	   在内存中做分组聚合。结果为：北京：40、深圳：60